
# Version 1.0    <2008-05-29    cookie
# Version 2.0    2009-01-07    Michael (cookie)
# - changed interface
# - deleted AnimNew Class
# - removed "sprite.py" dependency
# - removed circle based collision found in the previous version
# - ANIM.DAT changed to "ANIMV2.DAT" to support both versions
# Version 2.1     2009-01-08    Michael (cookie)
# - added times between frames on a per frame basis (optional)
# - keeps already loaded anims until discarding in memory:
#     - warning: Clean up manually
#     - warning: ignores changed "preserveralpha" or "loadImages" Paramters
#     - warning: works on a per filename basis (not hashes!!)
#     - set: keep_in_memory = False to disable
# Version 2.2     2009-01-10    Michael (cookie)
# - added support for colorkey_transparency
# - added minimum support for permanent decompressor

# Version 3.0     2009-01-18     MIchael (cookie)
# - completely overhauled 
# - New Interfaces for all classes, INCOMPATIBLE!
# - new features:
# - Playback speed can be slowed don and speed up by a speed_factor
# ToDO:
# - Docs
# - Optimzide Resource manager (use hash)
# - multiple animdats per animation ??
# - implement "load from filesystem"
# - interface for decompression is not cool

# Version 3.1     2009-03-11    Michael
# added flexible parsing of anim_dat (register_command)

__VERSION__ = "3.1"

import sys, os, pygame, tarfile
import decompressor

ANIMDATFILENAME = "anim3.dat"
DEBUG = False

class __ResMgr (object):
    def __init__(self):
        self._res = {}
       
    def get_from_cache (self, tarfilename):
        try:
            s = self._res [tarfilename]
        except KeyError:
            return None
        return s
    
    def add_to_cache (self, tarfilename, animdat):
        self._res [tarfilename] = animdat

    def flush (self):
        self.res = {}
        
__rsmgr__ = __ResMgr ()

#TODO: Put this Hotspot as extension somewhere else
class Hotspot (object):
    """ 
    Command "hotspot" in additional anim3.dat
    """
    def __init__(self, x, y):
        self.hotspot = (x,y)
        

class Frame (object):
    def __init__(self, number, name, time):
        self.id      = -1
        self.number  = number
        self.image   = None
        self.name    = name
        self.time    = time
        
    def create_id (self):
        self.id = hash(self.image)
        
class Set_Parameter (object):
    def __init__(self, use_alpha=True, preserve_alpha=True, colorkey=(0,0,0), scale=1.0): 
        self.use_alpha = use_alpha
        self.preserve_alpha = preserve_alpha
        self.colorkey = colorkey
        self.scale = scale

class AnimDat (object):
    ''' Infos about the animation, read from a file'''
    def __init__(self):
        self.frames = []
        self._archivename = None
        self._foldername = None
        self._params = None
        self.__archive = None
        self.__commands = []
        
    def __del__(self):
        if self.__archive != None:
            self.__close_archive()
            
    def load_from_archive (self, archivename, decompress=False):
        self._archivename = archivename
        if decompress:
            self.decompress(archivename)
        
        file = self.__load_tar(archivename, ANIMDATFILENAME)
        framecount = self.__parse_animdat(file)
        self.__load_frames()
        if DEBUG: print "Parsed", framecount, "frames"
        
    def load_from_folder (self,  foldername):
        print "Not implemented"
        pass
        
            
    def decompress (self, filename):
        try:
            decompressor.decompress (filename)
        except:
            if DEBUG: print "Anim: Error decompressing", filename

    def _register_command (self, commands):
        self.__commands = commands

    def __parse_animdat (self, file):
        """
        Parse animdat File and creates frames in self.frames, returns number of frames created
        Commands:
        Frame -> call "Frame" constructor and append to self.frames
        Set_Parameter -> call "Set_Parameter" constructor and modify self._params
        """
        line = 0
        for l in file:
            statemnt = ""
            # Pre-Process command
            line_has_valid_command = False
            for c in self.__commands:
                str_len, class_name, pre_string, post_string = c
                if l[:str_len] == class_name: 
                    l = l.replace("\n","")
                    statemnt = pre_string + l + post_string 
                    line_has_valid_command = True
            if not line_has_valid_command: continue
            # Execute Statement
#            try:
            exec statemnt
#            except:
#                if DEBUG: print "ERR: Error parsing line", line, "in file", file
#                return None
            line += 1
        return len(self.frames)
            
    def __load_frames (self):
        if DEBUG: print "...got ", len (self.frames), "frames"
        
        for frame in self.frames:
            if self._archivename != None:
                f = self.__load_tar (self._archivename, frame.name)
            elif self._foldername != None:
                f = self.__load_plain (frame.name)
            else:
                return None
            if self._params.use_alpha:            
                if self._params.preserve_alpha:
                    frame.image = pygame.image.load(f, frame.name).convert_alpha()
                else:
                    frame.image = pygame.image.load(f, frame.name).convert()
            else:   # use colorkey
                frame.image = pygame.image.load(f, frame.name).convert()
                frame.image.set_colorkey (self._params.colorkey, pygame.RLEACCEL)

            if self._params.scale != 1.0:
                r = frame.image.get_rect()
                frame.image = pygame.transform.smoothscale (frame.image, (r.w * self._params.scale, r.h * self._params.scale)) 

    def __load_plain (self, filename):
        "Load a file from disk"
        pass
       
    def __open_archive (self, tarfilename):
        if tarfile.is_tarfile (tarfilename) == False:
            if DEBUG: print "ERR:", tarfilename, "is not a tarfile"
            return None                     
        self.__archive = tarfile.open (tarfilename)
        
    def __close_archive (self):
        if self.__archive != None:
            self.__archive.close()
        
    def __load_tar (self, tarfilename, filename):
        "Extract a file from a tar archive (compressed or uncompressed, returns a file descripter or None"
        if self.__archive == None:
            self.__open_archive (tarfilename)
        
        try:
            f = self.__archive.extractfile (filename)
        except:
            if DEBUG: print "ERR: can not read", filename, "from", tarfilename
            self.__close_archive()
            return None

        return f

class Anim (object):
    def __init__(self):
        self.frameindex         = 0             # current frame index
        self.framecount         = 0             # total no of frames
        self._play              = False         # Bool: _playing status
        self._playback_speed    = 1.0           # Factor multiplies with frame speed
        self.loop               = True          # Bool: _play endless loop or only 1
        self.frames             = []            # Animation Frame Surfaces
        self.tbf                = 150           # time between frames, if not set in AnimDat per Frame 
        self.tmp_time           = 0             # tmp. var to keep track of time
        self.animdat            = None          # AnimDat-Object if not None
        self.__commands         = []
        
        self.register_command("Frame", "self.frames.append(", ")")
        self.register_command("Set_Parameter", "self._params=","")
        
    def load (self, filename=None, keep_in_memory=True):
        if keep_in_memory:
            # lookup in cache
            self.animdat = __rsmgr__.get_from_cache(filename)
            # if we did not find it in cache, load it
            if self.animdat == None:
                self.animdat = AnimDat ()
                self.animdat._register_command(self.__commands)
                self.animdat.load_from_archive (filename)
                __rsmgr__.add_to_cache (filename, self.animdat)
        else:
            # create a new object
            self.animdat = AnimDat()
            self.animdat._register_command(self.__commands)
            self.animdat.load_from_archive(filename)
        self.frames = self.animdat.frames
        self.framecount = len(self.frames)
        
    def register_command (self, class_name, pre_string, post_string):
        t = ( len(class_name), class_name, pre_string, post_string)
        self.__commands.append (t)
        
    def __repr__(self):
        return "<Anim("+str(self.framecount)+" frames)>"

    def _next_frame (self):
        "sets current frame += 1, considers loops etc"
        if self.frameindex == self.framecount-1:
            self.frameindex = 0
            if self.loop == False:
                self._play = False
            return self.frameindex
        self.frameindex += 1
        return self.frameindex 
    
    def get_frame(self, time):
        "Returns frame for the given time, first call sets time internally, returns None if no animation is loaded"
        if self.framecount == 0: return None
        if self.tmp_time == 0:
            self.tmp_time = time
            return self.frames[self._next_frame()]
        
        if self.is_playing():
            time_delta = self.tbf * self._playback_speed
            if self.frames[self.frameindex].time != -1:
                time_delta = self.frames[self.frameindex].time * self._playback_speed
            if (self.tmp_time + time_delta) <= time:
                self.tmp_time = time
                return self.frames[self._next_frame()]
                
        return self.frames[self.frameindex]
            
    def get_frame_rect(self):
        '''Returns surface.Rect object of frame 0'''
        if self.framecount > 0:
            return self.frames[self.frameindex].image.get_rect()
        else:
            return None
 
    # Methods to control _playback
    
    def do_loop (self, enable=True):
        self.loop = enable
    
    def __set_speed (self, speed=1.0): self._playback_speed = speed
    def __get_speed (self): return self._playback_speed
    speed = property (__get_speed, __set_speed, doc="Speed: 0 < x < 1 = faster  /  1 < x < infinte = slower")
    
    def play (self):
        "Activates playback for animation with given speed factor (default 1.0)"
        self._play = True  
    
    def pause (self):
        "Pauses animation"
        self._play = False
        
    def stop (self):
        "Stops animation, rewinds and resets everthing"
        self._play = False
        self.tmp_time = 0
        self._playback_speed = 1.0
        self.rewind()
    
    def rewind (self):
        "Rewind animation -> start playback at first frame"
        self.frameindex = 0

    def is_playing (self):
        return self._play
        

 
 
 